<?php
namespace Core\Common\Model;

use Core\Common\Helper\CacheLoader;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Query\Builder as QueryBuilder;
use Illuminate\Support\Arr;

/**
 * Class CachedBuilder
 * @package App\Model\Base
 */
class CachedBuilder extends Builder
{
    /**
     * 默认缓存时长
     */
    const DEFAULT_TTL = 86400;

    /**
     * @var CacheLoader
     */
    public $loader;

    /**
     * 缓存键
     * @var string
     */
    protected $cacheKey;

    /**
     * 指定本次查询是否进行缓存
     * @var bool
     */
    protected $remember = false;


    public function __construct(QueryBuilder $query, CacheLoader $loader)
    {
        parent::__construct($query);

        $this->loader = $loader;
    }

    /**
     * 设置本次查询需要缓存
     * @param null|int $ttl 缓存秒数，不传时使用Model默认的缓存描述, -1为不过期
     * @param null|string $key 缓存键
     * @return $this
     */
    public function remember($ttl = null, $key = null)
    {
        $this->remember = true;

        if (!is_null($ttl)) {
            $this->loader->setOption('ttl', $ttl);
        }
        if (!is_null($key)) {
            $this->cacheKey = $key;
        }
        return $this;
    }

    /**
     * 设置缓存tag
     * @param string[] $names
     * @return $this
     */
    public function cacheTags($names)
    {
        $this->loader->setOption('tags', $names);
        return $this;
    }

    /**
     * 获取缓存键
     * @return string
     */
    public function getCacheKey()
    {
        return $this->cacheKey ?: hash('sha256', $this->toSql().serialize($this->getBindings()));
    }

    public function get($columns = ['*'])
    {
        if (!$this->remember) {
            return parent::get($columns);
        }
        return $this->getCached($columns);
    }

    public function getCached($columns = ['*'])
    {
        $key = $this->getCacheKey();

        $ret = $this->loader->cached($key, function() use ($columns) {
            $this->remember = false;
            return $this->get($columns);
        });

        return $ret;
    }

    /**
     * 主键查询，默认以主键为缓存键
     * @param mixed $id
     * @param array $columns
     * @return Builder|Builder[]|Collection|\Illuminate\Database\Eloquent\Model|null
     */
    public function find($id, $columns = ['*'])
    {
        if (empty($id)) {
            return null;
        }
        if (!$this->remember) {
            return parent::find($id, $columns);
        }

        $object = $this->loader->cached($id, function($id) {
            $this->remember = false;
            $result = $this->find($id);
            return $result;
        });
        return $object;
    }

    /**
     * 以主键为缓存键批量查询
     * @param array $ids
     * @param array $columns
     * @return Collection
     */
    public function findMany($ids, $columns = ['*'])
    {
        if (empty($ids)) {
            return new Collection([]);
        }
        if (!$this->remember) {
            return parent::findMany($ids, $columns);
        }

        $objects = $this->loader->cachedMulti($ids, function($ids) {

            $this->remember = false;

            $pk = $this->getModel()->getKeyName();
            $objects = $this->findMany($ids)->keyBy($pk)->all();

            $result = [];
            foreach ($ids as $id) {
                $result[] = Arr::get($objects, $id, null);
            }
            return $result;
        });

        return Collection::make($objects)->filter(function($item) { return !is_null($item); })->values();
    }

    public function cached($key, $missLoader, $options = [])
    {
        return $this->loader->cached($key, $missLoader, $options);
    }

    public function cachedMulti($keys, $missLoader, $options = [])
    {
        return $this->loader->cachedMulti($keys, $missLoader, $options);
    }

    public function forget($key)
    {
        $this->loader->forget($key);
    }

    public function forgetMulti($keys)
    {
        $this->loader->forgetMulti($keys);
    }

    public function setCache($key, $value)
    {
        $this->loader->set($key, $value);
    }

    public function setCacheMulti($items)
    {
        $this->loader->setMulti($items);
    }

    public function flushCache($cacheTags = [])
    {
        $this->loader->flushTaggedCache($cacheTags);
    }
}